#pragma once
#include <Rz/Object.h>
#include <Rz/Graphics/Math/Vector2.h>
#include <Rz/Graphics/Math/Vector3.h>
#include <Rz/Graphics/Math/Matrix4.h>
#include <Rz/Graphics/Materials/MaterialBase.h>
#include <Rz/Graphics/Render/IDeviceResource.h>
#include <Rz/Graphics/Render/GBuffer.h>
#include <Rz/Graphics/Render/Viewport.h>
#include <Rz/Graphics/Render/FullScreenQuad.h>
#include "ICompositionAdapter.h"

namespace Rz { namespace Graphics { namespace Render { namespace CompositionAdapters {

class DeferredComposition :
	public Object,
	public ICompositionAdapter
{
	public:
		RZ_RTTI(Rz::Graphics::Render::CompositionAdapters, DeferredComposition);
		RZ_RTTI_IMPLEMENTS(Object, ICompositionAdapter);
		RZ_RTTI_END;

		DeferredComposition()
		{

		}

		void SetViewport(Viewport* viewport) override
		{
			ICompositionAdapter::SetViewport(viewport);

			_gbuffer = this->GetRenderDevice()->CreateGBuffer(this->GetViewport()->GetSize());
			_quad = new FullScreenQuad(this->GetRenderDevice());

			_quadShader = this->GetRenderDevice()->CreateEffect("FullScreenQuad");
// 			_inputTexture = _quadShader->CreateAndAddProperty("uInputTexture0");
// 			_inputTexture->Set(_gbuffer->GetColorBuffer());

			_vm = _quadShader->CreateAndAddProperty("uViewMatrix");

			_quadShader->CreateAndAddProperty("uInputTexture0")->Set(_gbuffer->GetColorBuffer());
			_quadShader->CreateAndAddProperty("uInputTexture1")->Set(_gbuffer->GetNormalBuffer());
			_quadShader->CreateAndAddProperty("uInputTexture2")->Set(_gbuffer->GetDepthBuffer());
		}

		void Draw(Scene::Node::Collection& scene) override
		{
			ActualizeInternals();

			this->GetRenderDevice()->SetRenderTargets({ _gbuffer->GetColorBuffer(), _gbuffer->GetNormalBuffer(), _gbuffer->GetDepthBuffer() });
			this->GetRenderDevice()->Clear(ClearOptions::Target | ClearOptions::DepthBuffer, Math::Vector4(0.0f, 0.0f, 0.0f, 1.0f));

			for (auto& e : scene)
			{
				this->RenderNode(e);
			}

			this->GetRenderDevice()->SetRenderTarget(nullptr);
			this->GetRenderDevice()->Clear(ClearOptions::Target | ClearOptions::DepthBuffer, Math::Vector4(0.0f, 0.0f, 0.0f, 1.0f));

			_quadShader->Bind();
			_vm->Set(_viewMatrix);
			_quad->Draw();
		}

	private:
		void ActualizeInternals()
		{
			if (this->GetCamera() != nullptr)
			{
				_projectionMatrix = this->GetCamera()->GetProjection().GetMatrix();
				_viewMatrix = this->GetCamera()->GetWorldMatrix();
				_projectionViewMatrix = _projectionMatrix * _viewMatrix;

				_nearFar = this->GetCamera()->GetProjection().GetNearFar();

				const Math::Matrix4& inverseViewMatrix = _viewMatrix.Inverted();
				_eyeDirection = inverseViewMatrix.GetTranslation().Negated().Normalized();
				_eyePosition = inverseViewMatrix.GetTranslation();
			}

			if (this->GetViewport() != nullptr)
			{
				_windowSize = Math::Vector2(
					static_cast<f32>(this->GetViewport()->GetSize().GetWidth()),
					static_cast<f32>(this->GetViewport()->GetSize().GetHeight()));
			}
		}

		void RenderNode(Scene::Node* node)
		{
			switch (node->GetNodeType())
			{
				case Scene::NodeType::Mesh:
					{
						Scene::MeshNode* mesh = node->As<Scene::MeshNode*>();

						mesh->GetMaterial()->Apply();

						mesh->GetMaterial()->SetEffect(_gbuffer->GetShader());
						mesh->GetMaterial()->pProjectionMatrix->Set(_projectionMatrix);
						mesh->GetMaterial()->pViewMatrix->Set(_viewMatrix);
						mesh->GetMaterial()->pModelMatrix->Set(mesh->GetWorldMatrix());
						mesh->GetMaterial()->pProjectionViewMatrix->Set(_projectionViewMatrix);
						mesh->GetMaterial()->pProjectionViewModelMatrix->Set(_projectionViewMatrix * mesh->GetWorldMatrix());

						mesh->GetMaterial()->pNormalMatrix->Set(mesh->GetNormalMatrix());
						mesh->GetMaterial()->pEyeDirection->Set(_eyeDirection);
						mesh->GetMaterial()->pEyePosition->Set(_eyePosition);
						mesh->GetMaterial()->pWindowSize->Set(_windowSize);
						mesh->GetMaterial()->pNearFar->Set(_nearFar);

						mesh->GetGeometry()->GetRenderDevice()->DrawIndexedPrimitives(Render::PrimitiveType::TriangleList, mesh->GetGeometry()->GetVertexArray());
					}
					break;

				default:

					break;
			}
		}

		Ptr<GBuffer> _gbuffer;
		Ptr<FullScreenQuad> _quad;
		Ptr<IEffect> _quadShader;
		Ptr<IEffectProperty> _vm;

		Math::Matrix4 _projectionMatrix;
		Math::Matrix4 _viewMatrix;
		Math::Matrix4 _projectionViewMatrix;

		Math::Vector3 _eyeDirection;
		Math::Vector3 _eyePosition;

		Math::Vector2 _nearFar;
		Math::Vector2 _windowSize;
};

} } } }
